package panda.natalia.gothread.suspend.instrument;


import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.analysis.*;

/**
 * a simple framework of java fiber
 *
 * @Author: scpanda2007
 * @Date: 2019/11/28 16:00
 *
 * asm 的 TypeAnalyzer
 *
 */
public class TypeAnalyzer extends Analyzer {

    public TypeAnalyzer(MethodDatabase db) {
        super(new TypeInterpreter(db));
    }

    /**
     *
     * +--------------------------------+
     * | boolean   |  Z                 |
     * | char      |  C                 |
     * | byte      |  B                 |
     * | short     |  S                 |
     * | int       |  I                 |
     * | float     |  F                 |
     * | long      |  J                 |
     * | double    |  D                 |
     * | Object    |  Ljava/lang/Object;|
     * | int[]     |  [I                |
     * | Object[][]|  [[Ljava/lang/Object;|
     * eg.
     *  INVOKESTATIC android/util/Log.i(Ljava/lang/String;Ljava/lang/String;) I
     *
     *  大多数情况下,一个字符代表一个参数(jvm很惜字如金),如果是L需要跳到;之后
     *  如果是[表示数组需要一直读取到下一个[
     *
     * @param methodDescript
     * @return
     */
    static int getNumArguments(String methodDescript){
        int off = 1;
        int size = 0;
        while (true) {
            char character = methodDescript.charAt(off++);
            if (character == ')') return size;

            if (character != '[') {
                ++size;
                if (character=='L') {
                    off = methodDescript.indexOf(";", off) + 1;
                }
            }
        }
    }

    static class TypeFrame extends Frame {

        TypeFrame(int nLocals, int nStack) {
            super(nLocals, nStack);
        }

        TypeFrame(Frame src) { super(src); }

        @Override
        public void execute(AbstractInsnNode insnNode, Interpreter interpreter) throws AnalyzerException {

            // 这段函数 模拟了 一个 frame 分析完 一段函数执行 的流程，相当于模拟了一段状态变化 （取出参数和各种值 以及向堆栈 压入返回值）（应该是用于记录各个frame的堆栈状态）
            // 通过查看 super.execute 可以知道各种code的变化
            // 不太清楚, 除了 <init> 函数似乎要覆盖之外，为什么要覆盖另外几种调用
            // 我对比了实际的原代码, 这里除了 <init> 之外, 其他几种情况实际上是相同的结果，
            // 他这里只是简单了省略了原来的一些用不到的步骤, 目前看起来算是一种优化 不知道是否有显著提升，否则个人觉得应该用原来的方法，方便理解
            // TODO: 考虑只覆写 <init> 函数的情况, 保持代码的简洁易懂, getNumArguments 参考的 asm 自己的 Type.getNumArguments \
            // 只不过本身的值 还分析了 每个参数的具体类型 Type
            switch (insnNode.getOpcode()){
                case Opcodes.INVOKEVIRTUAL:
                case Opcodes.INVOKESPECIAL:
                case Opcodes.INVOKESTATIC:
                case Opcodes.INVOKEINTERFACE: {
                    String descript = ((MethodInsnNode) insnNode).desc;
                    Type.getArgumentTypes(descript);
                    for(int i = getNumArguments(descript); i > 0 ; i--){
                        pop(); // 自顶向下, 弹出参数
                    }

                    if (insnNode.getOpcode() != Opcodes.INVOKESTATIC) {
                        pop(); // 这个地方应该是弹出函数的调用对象, 静态函数调用是没有这个值的
                        /**
                         * eg.
                         * new TestCode(-1);
                         *
                         *  LINENUMBER 19 L0
                         *     NEW panda/natalia/gothread/suspend/instrument/TestCode
                         *     DUP
                         *     ICONST_M1
                         *     INVOKESPECIAL panda/natalia/gothread/suspend/instrument/TestCode.<init> (I)V
                         *     POP
                         *
                         *  对于 new 函数, 在前面弹出了 参数 -1, 然后 本行代码的pop(); 应该是弹出了 DUP;
                         *  下面判断里 pop() 的对象 应为 new 的对象, 在这里做了一次处理。将 NewValue 做了一次还原
                         *
                         *  如果new的值没被赋值，会在最末自动添加一句POP
                         *
                         */
                        if (insnNode.getOpcode() == Opcodes.INVOKESPECIAL && getStackSize() > 0) {
                            // INVOKESPECIAL 为 构造函数（初始化） 父类方法 私有方法等 （这三类方法在编译时可以确定）
                            // 即使是一个无参的由jvm自动生成的构造函数，其StackSize为1，ArgSize为1，这个
                            // 变量对象应该是值的系统生成了一块指向内容的地址，用于存放创建的新对象
                            if ("<init>".equals(((MethodInsnNode) insnNode).name)) {
                                Value value = pop();
                                if (value instanceof NewValue){
                                    value = new BasicValue(((NewValue)value).getType());
                                }
                                push(value);
                            }
                        }
                    }

                    Type returnType = Type.getReturnType(descript);
                    if(returnType != Type.VOID_TYPE){
                        push(interpreter.newValue(returnType));
                    }
                    break;
                }
                default:
                    super.execute(insnNode, interpreter);
            }

        }

    }

}
